package com.shijie.judgeBox;

import com.shijie.entity.Submit;
import com.shijie.exception.JudgeException;
import com.shijie.judgeBox.annotation.JudgeCodeType;
import com.shijie.utlis.JsonUtils;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.IOAccess;
import org.springframework.stereotype.Component;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
@Component
@NoArgsConstructor
@JudgeCodeType(value = Submit.CodeType.JavaScript)
public class JudgeWorkerJavaScript implements JudgeWorkerCreator.JudgeWorker {
    private static final String LANGUAGE = "js";


    @Override
    public Integer[] working(String code, Submit.CodeType codeType,
                             Class<?>[] paramsType, List<List<?>> params,
                             Class<?> resultType, List<List<?>> results) {

        String functionName = getFunctionName(code);

        int[] usageInfo = this.execute(code, functionName, paramsType, params, resultType, results);

        return new Integer[]{usageInfo[0], usageInfo[1]};
    }


    /**
     * 获取方法名，主函数必须在第一个位置
     *
     * @param code String
     * @return String
     */
    private String getFunctionName(String code) {
        String functionName = "solving";
        String[] split = code.split(" ");
        for (int i = 0; i < split.length; i++) {
            if (split[i].equals("const")) {
                do i++;
                while (split[i].isBlank());
                functionName = split[i].split("=")[0].trim();
                break;
            }
        }

        return functionName;
    }

    private int[] execute(String code, String funcName,
                          Class<?>[] paramsType, List<List<?>> params,
                          Class<?> resultType, List<List<?>> results) {

        long useTimeNano = 1L; //纳秒 1ms = 1_000_000ns
        long useMemoryByte = 1L; //字节
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        List<List<?>> targetResults = new ArrayList<>();
        // 开始执行编译
        try (Context context = Context.newBuilder(LANGUAGE)
                .allowAllAccess(false)
                .allowIO(IOAccess.NONE).allowHostAccess(HostAccess.ALL).build()) {
            context.eval(LANGUAGE, code);
            Value member = context.getBindings(LANGUAGE).getMember(funcName);

            // 参数格式化
            for (List<?> list : params) {
                Object[] args = list.toArray();
                for (int i = 0; i < args.length; i++)
                    args[i] = JsonUtils.argvCast(args[i], paramsType[i]);

                // 方法计时与内存
                long beginT = System.nanoTime();
                MemoryUsage beginMUsage = memoryBean.getHeapMemoryUsage();
                Value value = member.execute(args);
                long endT = System.nanoTime();
                MemoryUsage endMUsage = memoryBean.getHeapMemoryUsage();
                useTimeNano += (endT - beginT);
                useMemoryByte += (endMUsage.getUsed() - beginMUsage.getUsed());

                targetResults.add(List.of(this.valueFormat(value, resultType)));
            }
        } catch (Exception e) {
            log.error(e.toString());
            throw new JudgeException("js编译失败");
        }

        // 进行比较
        JudgeWorkerCreator.JudgeWorker.compare(resultType, results, targetResults);

        int useTimeMs = Integer.parseInt(Long.toString(useTimeNano / 1_000_000)); //ms
        int useMemoryKb = Integer.parseInt(Long.toString(useMemoryByte / 1024)); //kb

        return new int[]{useTimeMs, useMemoryKb};
    }

    private Object valueFormat(Value value, Class<?> resultType) {
        if (value == null || value.isNull()) return null;

        if (resultType.isArray() || resultType.equals(List.class))
            return Arrays.asList(value.as(Object[].class));

        return value.as(resultType);
    }

}
